/*
 * @Description: 微前端加载；判断路由渲染
 * @Version: 1.0
 * @Autor: chen_yt
 * @Date: 2021-01-12 13:48:25
 * @LastEditors: chen_yt
 * @LastEditTime: 2022-09-20 15:43:25
 */
import Vue from 'vue'
import { resetMessage } from '@/utils/message'
import { deepClone } from '@/utils/util'
import {
  registerMicroApps,
  start,
  addGlobalUncaughtErrorHandler,
} from 'qiankun'

//微前端相关设置
import {
  appPathPrefix,
  requestTime,
  maxRequestTry,
  loadingContainer,
  __QIAN_KUN_CONTAINER__
} from '@/config'

import store from '@/store'
import { SUB_APP_LIST } from '@/store/Getter/getterTypes'
import { ADD_SUB_APP } from '@/store/Mutation/mutationTypes'

//需要传递给子应用的方法
import { getUserRoutes } from '@/utils/route'
import { getStoragePrefix } from '@/utils/storage'
import request from '@/utils/request'
import { getUrl, getSign } from '@/utils/request/sign'
import { nodePermission, dataPermission } from '@/permission'

var LOADING = null //LOADING弹窗
const getActiveRule = (hash) => (location) => location.hash.startsWith(hash)

/**
 * @description: 根据注册在config/microapp/subapps中的子应用名称
 *               判断该path是否为子应用的路由
 */
export function isRouteBelongToSubApp(path) {
  return getSubAppList().some(item => { return path.indexOf(item.activeRoute) > -1 })
}


export function getSubAppList() {
  return store.getters[SUB_APP_LIST].map(item => deepClone(formatAppInfo(item)))
}

export function getAppList() {
  return getSubAppList()
}

/**
 * @description: 手动注册一个子应用
 *               所有子应用的name需要唯一
 */
export function registerExtraApp(app) {
  //name检测
  if (!app || !app.name) {
    console.error('子应用名称不能为空')
    return false
  }

  //同名子应用检测
  if (getSubAppList().some(item =>
    item.name == app.name
  )) {
    console.error('同名子应用已存在')
    return false
  }

  let formatApp = formatAppInfo(app)

  //记录新注册的子应用
  store.commit(ADD_SUB_APP, formatApp)

  registerMicroApp([formatApp])

  return true
}


/**
 * @description: 该对象会混入layout中的AppMain        
 *               
 */
export default {
  name: 'QianKun',
  computed: {
    LOADING() {
      return LOADING
    },
  },
  data() {
    return {
      load404App: {}, //加载失败的子应用
      retryTimer: {}, //重连定时器
      alreadyMounted: false,
      tryRequestNum: 0,
    }
  },
  methods: {
    /**
     * @description: qiankun初始化，包括以下步骤
     *               1、注册初始的子应用
     *               2、添加子应用异常捕获
     *               3、在window上挂载一点东西
     *               4、启动qiankun
     */
    initQiankun() {
      if (this.alreadyMounted) {
        return
      }
      this.alreadyMounted = true

      registerMicroApp(getSubAppList())

      addGlobalUncaughtErrorHandler(e => {
        if (e.error?.appOrParcelName) {
          console.log('子应用加载出错:', e)
          resetMessage.error('填写正确的项目地址或者请部署子应用')
          let currentApp = getSubAppList().find(item => item.name == e.error.appOrParcelName)
          if (currentApp) {
            let key = getBasePath(currentApp.activeRoute)
            this.load404App[key] = currentApp.entry
          }
        }
      })

      addSpecialConfigToWindow()

      start({
        fetch: this.request, // 自定义fetch方法
        prefetch: false, //关闭预加载
        sandbox: true,
      })
    },

    /**
     * @description: 子应用的首个请求时候显示loading并重置重连尝试次数
     *               判断方法：首个fetch的请求地址与entry一致         
     */
    request(url) {
      // console.log('url:' + url)
      let isEntry = getSubAppList().some(item => item.entry === url)
      if (isEntry) {
        showLoading()
        this.tryRequestNum = 0
      }
      return this.newRequest(url, getBasePath(this.$route.path))
    },


    newRequest(url, mountedBaseRoute) {
      let that = this
      let key = getBasePath(this.$route.path)
      if (mountedBaseRoute !== key) {       //该计时器触发时路由已跳出所在子应用，停止fetch
        return
      }

      return new Promise((resolve, reject) => {
        fetch(url, {
          headers: {
            'Access-Control-Request-Method': '*'        //添加跨域
          },
          referrerPolicy: 'origin-when-cross-origin'
        })
          .then(res => {
            if (res.status != 200) {     // 子应用未部署情况
              hideLoading()
              resetMessage.error('填写正确的项目地址或者请部署子应用')
              that.load404App[key] = url // 将其url添加404url中 用于下次再点击提示
              resolve()     // 返回状态给qiankun
              return
            }
            that.tryRequestNum = 0
            if (that.retryTimer[key]) {      // 子应用加载成功，清除重连列表
              window.clearTimeout(that.retryTimer[key].timer)
              delete that.retryTimer[key]
            }
            if (that.load404App[key]) {    // 子应用加载成功，清除错误记录列表
              delete that.load404App[key]
            }
            resolve(res)        // 返回状态给qiankun 否则会一直pending中
          })
          .catch(err => {
            that.load404App[key] = url     // 加载失败，添加入失败列表        
            if (that.tryRequestNum < maxRequestTry) {     // 设置尝试的间隔时间，开始下一次尝试
              let timer = setTimeout(() => {
                that.tryRequestNum++
                if (LOADING) {
                  LOADING.setText(`正在重连应用第${that.tryRequestNum}次`)
                }
                that.newRequest(url, mountedBaseRoute)
              }, requestTime)
              that.retryTimer[key] = { timer, url }
            }
            else {     //重连达到最大次数
              resetMessage.error('连接失败')
              hideLoading()
              that.tryRequestNum = 0
            }
            reject(err)    // 返回加载失败状态给qiankun，去除pending状态
          })
      })
    },
  },
  watch: {
    //isRouteBelongToSubApp这个值来自被mixin的文件
    isRouteBelongToSubApp(val) {
      if (!val) {
        hideLoading()
      }
    },
    LOADING(val) {
      if (val && !this.isRouteBelongToSubApp) {
        hideLoading()
      }
    },
    $route(to, from) {
      if (!isRouteBelongToSubApp(from.path) && !isRouteBelongToSubApp(to.path)) {
        return       //与子应用切换无关时不watch
      }
      if (getBasePath(to.path) == getBasePath(from.path)) {
        return       //基础路由一致，说明在同一个子应用中，不处理
      }

      hideLoading()

      //从子应用切入或切出时，删除所有定时器，停止所有重连尝试
      //存在正在进行fetch的方法，在fetch里也需要控制
      for (let key in this.retryTimer) {
        console.log('删除重连定时器')
        window.clearTimeout(this.retryTimer[key].timer)
        this.retryTimer[key].timer = null
        delete this.retryTimer[key]
      }

      let key = getBasePath(to.path)
      if (this.load404App[key]) {    //进入了上次加载失败的子应用，触发重连
        console.log('进入了一个上次加载失败的子应用')
        this.request(this.load404App[key])
      }

    }
  },
  mounted() {
    if (!this.alreadyMounted) {
      setTimeout(() => { this.initQiankun() }, 1000)
    }
  },
  created() {
    hideLoading()
  },
  beforeDestroy() {
    hideLoading()
  }
}


/**
 * @description: 在window上整点特殊参数/方法,可供子应用使用
 */
function addSpecialConfigToWindow() {
  window.baseUrlForSubapp = Vue.prototype.VUE_APP_BASE_API
}

/**
 * @description: 调用qiankun注册子应用，并添加生命周期的钩子
 */
function registerMicroApp(microApps) {
  let apps = microApps.map(item => addMicroAppProps(item))
  registerMicroApps(
    apps,
    {
      beforeLoad: [
        app => {
          // console.log('beforeLoad')
          showLoading()
        }
      ],
      beforeMount: [
        app => {
          // console.log('beforeMount')
          showLoading()
        }
      ],
      afterMount: [
        app => {
          // console.log('afterMount')
          hideLoading()
        }
      ],
      beforeUnmount: [
        app => {
          // console.log('beforeUnmount')
        }
      ],
      afterUnmount: [
        app => {
          // console.log('afterUnmount')
        }
      ],
    },
  )
}

/**
 * @description: 补充并格式化appInfos中各项属性值
 */
function formatAppInfo(appInfo) {
  let newAppInfo = deepClone(appInfo)

  newAppInfo.entry = newAppInfo.entry
    ? newAppInfo.entry
    : '/' + appPathPrefix + newAppInfo.name + '/'

  newAppInfo.activeRoute = newAppInfo.activeRoute
    ? newAppInfo.activeRoute
    : '/' + appPathPrefix + newAppInfo.name

  newAppInfo.activeRule = getActiveRule('#' + newAppInfo.activeRoute)

  return newAppInfo
}

/**
 * @description:  注册子应用时添加需要传递的参数
 * @param {Object} appInfo
 * @return {Object} newAppInfo
 * @author: chen_yt
 */
function addMicroAppProps(appInfo = {}) {
  let newAppInfo = deepClone(appInfo)

  let SharedFunctions = {
    getUserRoutes,
    getStoragePrefix,
    request,
    getUrl,
    getSign,
    nodePermission,
    dataPermission
  }

  let props = {
    store,
    data: {},
    appName: newAppInfo.name,
    appBasePath: newAppInfo.basePath,
    func: SharedFunctions
  }
  newAppInfo.container = newAppInfo.container || __QIAN_KUN_CONTAINER__
  newAppInfo.props = { ...newAppInfo.props, ...props }

  return newAppInfo
}

function showLoading() {
  if (LOADING) {
    return
  }
  LOADING = Vue.prototype.$loading({
    lock: false,
    target: document.getElementsByClassName(loadingContainer)[0],
    spinner: 'fks-icon-loading',
    text: '正在加载中',
    color: '#fff',
    background: '#F1F2F6',
    customClass: 'app_load_loading'
  })
}

function hideLoading() {
  if (LOADING) {
    LOADING.close()
    LOADING = null
  }
}

/**
 * @description: 用来获取路由path的第一段
 */
function getBasePath(path) {
  return path.split('/')[1]
}